import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Set;


public class TRModel extends Model {
	Agent myAgent;
	Hashtable<AID,Double> trust;
	Hashtable<AID,Double> reputation;
	Hashtable<AID,Integer> history;
	Hashtable<AID,Integer> updates;
	private double weight;

	public TRModel(Agent a, ArrayList<AID> agents, double w) {
		super(agents);
		this.weight = w;
		this.myAgent = a;
		init();
		myAgent.addBehaviour(new ReputationRequestsServer());
	}
	
	private void init() {
		trust = new Hashtable<AID,Double>();
		reputation = new Hashtable<AID,Double>();
		history = new Hashtable<AID,Integer>();
		updates = new Hashtable<AID,Integer>();
		
		for (AID a : traders) {
			trust.put(a, new Double(0));
			reputation.put(a, new Double(0));
			history.put(a, new Integer(0));
			updates.put(a, new Integer(0));
		}
	}

	@Override
	public void updateReputation(AID trader) {
		myAgent.addBehaviour(new RequestReputation(trust.keySet(), trader));
	}

	@Override
	public void updateTrust(AID trader, int h, double modifier) {
		if (h != 1 && h != 0) return;
		
		Double trustValue = trust.get(trader);
		Integer hValue = history.get(trader);
		Integer uValue = updates.get(trader);
		
		if (trustValue == null || hValue == null || uValue == null) return;
		
		hValue += h;
		uValue ++;
		
		trustValue = weight * ((double) hValue / uValue) + (1 - weight) * modifier;
		
//		print("[" + trader.getLocalName() + ", " + weight + ", " + hValue + ", " + uValue + ", " + modifier + ", " + trustValue + "]");
		
		trust.put(trader, trustValue);
		history.put(trader, hValue);
		updates.put(trader, uValue);
		
//		System.out.println("new trust value for " + trader + ": " + trust.get(trader));
	}
	
	public double getReputation(AID trader) {
		return reputation.get(trader);
	}
	
	public double getTrust(AID trader) {
		return trust.get(trader);
	}
	
	private void print(String message) {
		System.out.println(myAgent.getLocalName() + " TRModel: " + message);
	}
	
	private class RequestReputation extends Behaviour {
		private Set<AID> receivers;
		private AID trader;
		private int repliesCnt = 0;
		private int step = 0;
		
		private double rep = 0;
		
		
		RequestReputation(Set<AID> r, AID t) {
			this.receivers = r;
			this.trader = t;
		}
		
		@Override
		public void action() {
			switch (step) {
			case 0:
//				print("sending request message for trader " + trader.getLocalName() + " to: " + receivers.toString());
				ACLMessage message = new ACLMessage(ACLMessage.REQUEST);
				for (AID aid : receivers) {
					if (aid.getLocalName().compareTo(trader.getLocalName()) == 0) continue;
					message.addReceiver(aid);
				}
				message.setConversationId("reputation-request");
				message.setContent(trader.getLocalName());
				myAgent.send(message);
				step = 1;
				break;
			case 1:
				MessageTemplate mt = MessageTemplate.and(MessageTemplate.MatchConversationId("reputation-request"),
						MessageTemplate.MatchPerformative(ACLMessage.INFORM));
				ACLMessage reply = myAgent.receive(mt);
				if (reply != null) {
					rep += trust.get(reply.getSender()) * Double.parseDouble(reply.getContent());
					repliesCnt ++;
					if (repliesCnt >= receivers.size() - 1) {
						reputation.put(trader, (double) rep / repliesCnt);
//						print("Reputation for " + trader.getLocalName() + ": " + rep);
						step = 2;
					}
				}
				else {
					block();
				}
				break;
			}
		}

		@Override
		public boolean done() {
			return (step == 2);
		}
	}
	
	private class ReputationRequestsServer extends CyclicBehaviour {

		@Override
		public void action() {
			MessageTemplate mt = MessageTemplate.and(MessageTemplate.MatchConversationId("reputation-request"),
					MessageTemplate.MatchPerformative(ACLMessage.REQUEST));
			ACLMessage msg = myAgent.receive(mt);
			
			if (msg != null) {
//				print("Received request from " + msg.getSender().getLocalName() + " for " + msg.getContent() + "<<<<<<");
				ACLMessage reply = msg.createReply();
				reply.setPerformative(ACLMessage.INFORM);
				reply.setContent("" + trust.get(new AID(msg.getContent(), AID.ISLOCALNAME)));
				myAgent.send(reply);
			}
			else {
				block();
			}
		}
		
	}
	
}
